﻿#define _CRT_SECURE_NO_WARNINGS
#include"CentralCache.h"
#include"PageCache.h"
#include"ThreadCache.h"

CentralCache CentralCache::_Singleton;  //类内静态成员要在类外初始化，而且为了防止出现重复定义
                                        //要将声明与定义头源分离。


// 从指定的SpanList中获取⼀个非空的Span
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{
    //1.获取指定SpanList下的第一个Span
    Span* _span = list.Begin();
    while (_span != list.End())
    {
        if (_span->_freelist != nullptr) //该Span有内存块
        {
            return _span;
        }
        _span = _span->_next;
    }

    //该SpanList的Span都没挂有内存块
    
    //2.该拥锁线程去向PageCache申请Span前先释放该SpanList的锁，
    // 因为有些线程需要用到该锁将ThreadCache的内存块还给该SpanList
    list._mtx.unlock();  //list._mtx 这个锁已经在调用 GetOneSpan 前锁上了


    //3.走到这里说明该SpanList没有非空的Span, 找下一层PageCache要
    //  3.1 要多少个页？（一个页4KB或8KB)

    //对PageCache的整个哈希桶加锁：
    PageCache::GetInstance()->_pagemtx.lock();
    Span* newspan=PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));
    //标记当前Span已经被CentralCache拿去使用，不能对它进行合并
    newspan->_isUse = true; 
    newspan->_objSize = size; //该span下的每块内存块的大小

    PageCache::GetInstance()->_pagemtx.unlock();


    //注意：切割大内存块这里不需要加SpanList的木板锁，因为这里不使用到共享资源
   
    //4.切割该大块内存块
    // 4.1 计算该newspan的大块内存的起始地址和总大小
    char* start = (char*)(newspan->_pageid << PAGE_SHIFT);  //大内存块的起始页页号*8KB
    size_t total_size = newspan->_n << PAGE_SHIFT;  //页数*8KB

    char* end = start + total_size;
    //4.2 先切一小块下来，方便尾插
    newspan->_freelist = start;
    start += size;
    void* tail = newspan->_freelist;

    int i = 1;
    while (start < end) //不能取等，因为start==end时，end指向的内存不属于我们
    {
        i++;
        NextObj(tail) = start;
        tail = start;
        start +=  size;
    }
    //注意切完后链表尾部要置空！！！
    NextObj(tail) = nullptr;

    //5. 将该span头插到当前SpanList上
    //这一步使用到了当前木板SpanList，需要加锁(该SpanList的锁)
    list._mtx.lock();
    list.PushFront(newspan);
    //注意：这个时候还不能解锁，一定要等到在FetchRangeObj函数末尾操作完这个newspan指向的Span后才能解锁！！！
    return newspan;
}

size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
    //0.计算线程想要的是哪块木板(自由链表)下的内存块
    size_t index = SizeClass::Index(size); 

    //1.因为是申请_spanlists[index]木板下的Span，所以需要先加_spanlists[index]木板下的锁
    _spanlists[index]._mtx.lock();

    //2.在该SpanList下获取一个非空的Span
    Span* span=GetOneSpan(_spanlists[index], size);
    assert(span);
    assert(span->_freelist);
   
    //3.遍历span的_freelist
    //  end最多走batchNum-1步(当span的内存块个数>batchNum)
    //  NextObj(end)不能为空 (当span的内存块个数<batchNum时，会越界)

    start = span->_freelist;
    end = start;
    size_t i = 0;
    size_t actualNum = 1; //至少有一个内存块

    while (i < batchNum - 1 && NextObj(end) != nullptr)
    {
        end = NextObj(end);
        ++i;
        ++actualNum;
    }


    //4.span的_freelist要重新指向end后面的内存块
    // 
    //注意！！！：此时操作的Span是_spanlists[index]木板下的Span，所以此时一定是加了
    //            _spanlists[index]木板下的锁的！！！
    span->_freelist = NextObj(end);
    span->_useCount += actualNum;  //记录该span分配了几个内存块给ThreadCache
    NextObj(end) = nullptr;

    //这个时候才能解锁：
    //解锁：解的是GetOneSpan函数末尾时加的锁，或者解的是调用GetOneSpan之前加的锁
    _spanlists[index]._mtx.unlock();

    return actualNum;
}

//ThreadCache将小内存块还给CentralCache
void BackToSpan(void*& start,SpanList& list)
{
    //1.到这里就是使用具体的某块木板了
    //  所以需要先加锁
    list._mtx.lock();

    while (start)
    {
        void* next = NextObj(start);
        //2.计算出start指向的当前小内存块属于哪个span
        Span* which_span = PageCache::GetInstance()->PtrToSpan(start);

        //3.将该小内存块头插进到该span下
        NextObj(start) = which_span->_freelist;
        which_span->_freelist = start;

        //4.回来一个小块内存,span下的_useCount--
        which_span->_useCount--;

        //5.如果 which_span->_useCount==0 ,说明该span给ThreadCache使用的小内存块都被还回来了
        //  那么CentralCache需要将该span还给PageCache！！！
        if (which_span->_useCount == 0)
        {
            //(1)将该span从SpanList中去除
            list.Erase(which_span);
            //(2)该span下面这些成员变量失效：(但页号也页数不能动，PageCache层需要用)
            which_span->_freelist = nullptr;
            which_span->_next = nullptr;
            which_span->_prev = nullptr;

            //(3)注意：在当前线程去到PageCache层归还span期间，为了不妨碍其他线程也向当前SpanList
            //      归还小内存块或者向当前SpanList申请内存，需要先释放该木板锁
            list._mtx.unlock();

            PageCache::GetInstance()->_pagemtx.lock();
            PageCache::GetInstance()->ReleaseSpanToPageCache(which_span);
            PageCache::GetInstance()->_pagemtx.unlock();

            //(4)回到这里后，想继续循环，必须先申请当前木板锁
            list._mtx.lock();
        }
        //5.到下一个要归还的小内存块
        start = next;
    }
    //6.解锁
    list._mtx.unlock();
}


//保存ThreadCache归还的一批内存块(一个该内存块的大小是size字节)
void CentralCache::ReleaseMemoryToCentralCache(void*& start, size_t size)
{
    //1.计算该内存块(已经对齐)属于哪个木板SpanList
    size_t index = SizeClass::Index(size);

    //2.多线程来到这里后，根据不同的index，去往不同的SpanList下
    BackToSpan(start, _spanlists[index]);

}
